#ifndef __ARRAY_H__
#define __ARRAY_H__

#include <cstdint>
#include <stdexcept>
#include <initializer_list>
#include <algorithm>
#include <ostream>

template<typename T>
class Array
{
public:
	Array(size_t len, const T& initVal = T())
 	    : m_size(len)
 	    , m_arr(new T[len])
	{
		for (size_t i = 0; i < len; i++)
		{
			m_arr[i] = initVal;
		}
	}
	Array(const std::initializer_list<T>& list)
	    : m_size(list.size())
	    , m_arr(new T[list.size()])
	{
		std::copy(list.begin(), list.end(), m_arr);
	}
	~Array() { destroy(); }	
	
	Array(const Array& array)
	    : m_size(array.m_len)
	    , m_arr(copy(array.m_arr))
	{
	}
	Array(Array&& array)
 	    : m_size(array.m_len)
 	    , m_arr(array.m_arr)
	{
		array.m_arr = nullptr;
		array->destroy();
	}
	Array& operator=(const Array& array)
	{
		if (this == &array)
		    return *this;
		destroy();
		m_size = array.size();
		m_arr = copy(array.data(), array.size());
	}
	Array& operator=(Array&& array)
	{
		if (this == &array)
		    return *this;
		destroy();
		m_size = array.size();
		m_arr = array.data();
		array.m_arr = nullptr;
		array.destroy();
	}
	
	class Iterator
	{
	public:
		explicit Iterator(T* iter): m_iter(iter) {}
		
		bool operator==(const Iterator& iter) const { return m_iter == iter.m_iter; }
		bool operator!=(const Iterator& iter) const { return m_iter != iter.m_iter; }
		T& operator*() const { return *m_iter; }
		T* operator->() const { return m_iter; }
		
		Iterator& operator++()
		{
			++m_iter;
			return *this;
		}
		Iterator operator++(int)
		{
			Iterator tmp = *this;
			++m_iter;
			return tmp;
		}
		Iterator& operator--()
		{
			--m_iter;
			return *this;
		}
		Iterator operator--(int)
		{
			Iterator tmp = *this;
			--m_iter;
			return tmp;
		}
	
	private:
		T* m_iter;
	};
	
	class ConstIterator
	{
	public:
		explicit ConstIterator(T* iter): m_iter(iter) {}
		
		bool operator==(const ConstIterator& iter) const { return m_iter == iter.m_iter; }
		bool operator!=(const ConstIterator& iter) const { return m_iter != iter.m_iter; }
		const T& operator*() const { return *m_iter; }
		const T* operator->() const { return m_iter; }
		
		ConstIterator& operator++()
		{
			++m_iter;
			return *this;
		}
		ConstIterator operator++(int)
		{
			ConstIterator tmp = *this;
			++m_iter;
			return tmp;
		}
		ConstIterator& operator--()
		{
			--m_iter;
			return *this;
		}
		ConstIterator operator--(int)
		{
			ConstIterator tmp = *this;
			--m_iter;
			return tmp;
		}
	
	private:
		T* m_iter;
	};
	
	class ReverseIterator
	{
	public:
		explicit ReverseIterator(T* iter): m_iter(iter) {}
		
		bool operator==(const ReverseIterator& iter) const { return m_iter == iter.m_iter; }
		bool operator!=(const ReverseIterator& iter) const { return m_iter != iter.m_iter; }
		T& operator*() const { return *m_iter; }
		T* operator->() const { return m_iter; }
		
		ReverseIterator& operator++()
		{
			--m_iter;
			return *this;
		}
		ReverseIterator operator++(int)
		{
			ReverseIterator tmp = *this;
			--m_iter;
			return tmp;
		}
		ReverseIterator& operator--()
		{
			++m_iter;
			return *this;
		}
		ReverseIterator operator--(int)
		{
			ReverseIterator tmp = *this;
			++m_iter;
			return tmp;
		}
	
	private:
		T* m_iter;
	};
	
	class ConstReverseIterator
	{
	public:
		explicit ConstReverseIterator(T* iter): m_iter(iter) {}
		
		bool operator==(const ConstReverseIterator& iter) const { return m_iter == iter.m_iter; }
		bool operator!=(const ConstReverseIterator& iter) const { return m_iter != iter.m_iter; }
		const T& operator*() const { return *m_iter; }
		const T* operator->() const { return m_iter; }
		
		ConstReverseIterator& operator++()
		{
			--m_iter;
			return *this;
		}
		ConstReverseIterator operator++(int)
		{
			ConstReverseIterator tmp = *this;
			--m_iter;
			return tmp;
		}
		ConstReverseIterator& operator--()
		{
			++m_iter;
			return *this;
		}
		ConstReverseIterator operator--(int)
		{
			ConstReverseIterator tmp = *this;
			++m_iter;
			return tmp;
		}
	
	private:
		T* m_iter;
	};
	
	void resize(size_t len, const T& initVal = T())
	{
		if (len == size())
		{
			for (auto& elem : m_arr)
			{
				elem = initVal;
			}
		}
		else
		{
		 	destroy();
		 	m_size = len;
		 	m_arr = newArr(len, initVal);
		}
	}
	template<typename ITER>
	void resize(const ITER& list)
	{
		if (list.size() != size())
		{
			destroy();
			m_size = list.size();
			m_arr = newArr(list.size()); 
		}
		std::copy(list.begin(), list.end(), m_arr);
	}
	
	Array copy() const
	{
		return Array(*this);
	}
	
	Iterator begin() { return Iterator(m_arr); }
	Iterator end() { return Iterator(m_arr + m_size); }
	ConstIterator begin() const { return ConstIterator(m_arr); }
	ConstIterator end() const { return ConstIterator(m_arr + m_size); }
	ReverseIterator rbegin() { return ReverseIterator(m_arr + m_size - 1); }
	ReverseIterator rend() { return ReverseIterator(m_arr - 1); }
	ConstReverseIterator rbegin() const { return ConstReverseIterator(m_arr + m_size - 1); }
	ConstReverseIterator rend() const { return ConstReverseIterator(m_arr - 1); }
	
	// implicit conversion
	operator T*()
	{
		return m_arr;
	}
	operator const T*()
	{
		return m_arr;
	}
	operator const T*() const
	{
		return m_arr;
	}
	
	T& operator[](int index)
	{
		return m_arr[index];
	}
	const T& operator[](int index) const
	{
		return m_arr[index];
	}
	T& at(int index)
	{
		if (index < 0 || index >= m_size)
		    throw std::out_of_range("index out of range");
		return m_arr[index];
	}
	const T& at(int index) const
	{
		if (index < 0 || index >= m_size)
		    throw std::out_of_range("index out of range");
		return m_arr[index];
	}
	
	size_t size() const { return m_size; }
	T* data() { return m_arr; }
	const T* data() const { return m_arr; }
	
	void print(std::ostream& os, const char *sep = ",", const char *prefix = "[", const char *suffix = "]") const
	{
		os << prefix;
		for (size_t i = 0; i < m_size; i++)
		{
			if (i)	os << sep;
			os << m_arr[i];
		}
		os << suffix;
	}
	
protected:
	void destroy()
	{
		if (m_arr) 
		    delete m_arr;
		m_arr = nullptr;
		m_size = 0;
	}
	T* copy(const T* arr, size_t len)
	{
		T *ans = new T[len];
		std::copy(arr, arr + len, ans);
		return ans;
	}
	T* newArr(size_t len)
	{
		return new T[len];
	}
	T* newArr(size_t len, const T& initVal)
	{
		T *arr = new T[len];
		for (const auto& elem : arr)
		{
			elem = initVal;
		}
		return arr;
	}
	
private:
	size_t m_size;
	T* m_arr;
};

#endif	// __ARRAY_H__
